﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace _3DTutorial_Models {
    class Camera {              

        public GraphicsDevice device;

        private Matrix view;       
        private Matrix projection;
        private Matrix rotation;

        private Vector3 cameraPosition;

        private float moveSpeed;
        private float rotateSpeed;
        private float yaw = 0;
        private float pitch = 0;

        int oldMousePositionX;
        int oldMousePositionY;

        MouseState mState;
        KeyboardState kState;

        public Camera(Vector3 cameraPosition, float moveSpeed, float rotateSpeed, GraphicsDevice device) {
            this.cameraPosition = cameraPosition;
            this.moveSpeed = moveSpeed;
            this.rotateSpeed = rotateSpeed;

            this.device = device;

            this.view = Matrix.CreateLookAt(cameraPosition, Vector3.Zero, Vector3.Up);
            this.projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45.0f), device.Viewport.AspectRatio, 0.01f, 1000.0f);

            // Startposition der Maus ist in der Mitte
            resetMouseCursor();
        }

        public void update() {
            kState = Keyboard.GetState();
            mState = Mouse.GetState();

            handleKeyboardInput();

            // Beschränkt die Pitch Bewegung auf ungefähr +- 90°
            pitch = MathHelper.Clamp(pitch, -1.5f, 1.5f);

            computeMouseMovement();
            resetMouseCursor();
            updateMatrices();
        }

        private void handleKeyboardInput() {
            // Die Bewegung der Kamera geschieht durch die WASD Tasten
            if (kState.IsKeyDown(Keys.W)) {
                Vector3 v = new Vector3(0, 0, -1) * moveSpeed;
                move(v);
            }

            if (kState.IsKeyDown(Keys.S)) {
                Vector3 v = new Vector3(0, 0, 1) * moveSpeed;
                move(v);
            }

            if (kState.IsKeyDown(Keys.A)) {
                Vector3 v = new Vector3(-1, 0, 0) * moveSpeed;
                move(v);
            }

            if (kState.IsKeyDown(Keys.D)) {
                Vector3 v = new Vector3(1, 0, 0) * moveSpeed;
                move(v);
            }
        }

        private void computeMouseMovement() {
            // Bestimme die Veränderung der Mausposition pro Frame
            // und setze yaw, pitch auf den neuen Wert

            int dx = mState.X - oldMousePositionX;
            yaw -= rotateSpeed * dx;

            int dy = mState.Y - oldMousePositionY;
            pitch -= rotateSpeed * dy;
        }

        private void resetMouseCursor() {
            // Bestimme die Mitte des Viewports
            int centerX = device.Viewport.Width / 2;
            int centerY = device.Viewport.Height / 2;

            // Setze die Maus auf die Mitte des Viewports
            Mouse.SetPosition(centerX, centerY);

            // Setze die Mausposition des vorherigen Frames auf die Mitte
            oldMousePositionX = centerX;
            oldMousePositionY = centerY;

        }

        private void updateMatrices() {
            rotation = Matrix.CreateRotationX(pitch) * Matrix.CreateRotationY(yaw);
            Vector3 transformedReference = Vector3.Transform(new Vector3(0, 0, -1), rotation);
            Vector3 lookAt = cameraPosition + transformedReference;

            view = Matrix.CreateLookAt(cameraPosition, lookAt, Vector3.Up);
        }

        private void move(Vector3 v) {
            // Für den Fall dass sich die Kamera um die y-Achse gedreht hat berechne den neuen Move
            // Vector v aus der Multiplikation der RotationsMatrix und dem MoveVector
            Matrix yRotation = Matrix.CreateRotationY(yaw);
            v = Vector3.Transform(v, yRotation);

            cameraPosition += v;
        }

        public Matrix getView() {
            return this.view;
        }

        public Matrix getProjection() {
            return this.projection;
        }
    }
}
